/*
 *  Copyright (c) 2014 James Buncle
 * 
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 * 
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 * 
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 * 
 */
package com.jbuncle.seleniumiderunner;

import static com.jbuncle.seleniumiderunner.Utils.startsWithIgnoreCase;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.script.ScriptException;
import junit.framework.TestCase;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author James Buncle
 */
public class TestDriver {

    private final WebDriver driver;
    private final Map<String, String> tokens;
    private final String UNSUPPORTED_ACTION = "The action %s is not currently support";
    private final com.jbuncle.seleniumiderunner.commands.Assert ASSERT_INSTANCE;
    private final com.jbuncle.seleniumiderunner.commands.Wait WAIT_INSTANCE;
    private final com.jbuncle.seleniumiderunner.commands.Basic COMMAND_INSTANCE;
    private URL baseURL;

    public TestDriver(final WebDriver driver) {
        this.driver = driver;
        this.tokens = new HashMap<>();
        this.COMMAND_INSTANCE = new com.jbuncle.seleniumiderunner.commands.Basic(this);
        this.ASSERT_INSTANCE = new com.jbuncle.seleniumiderunner.commands.Assert(this);
        this.WAIT_INSTANCE = new com.jbuncle.seleniumiderunner.commands.Wait(this);
    }

    public WebDriver getWebDriver() {
        return driver;
    }

    public URL getBaseURL() {
        return baseURL;
    }

    public void setBaseURL(final URL baseURL) {
        this.baseURL = baseURL;
    }

    public void storeVariable(final String name, final String value) {
        this.tokens.put(name, value);
    }

    protected String getVariable(final String name) {
        return this.tokens.get(name);
    }

    private String processTokens(final String template) throws ScriptException {
        final StringBuffer sb = new StringBuffer();
        final Matcher myMatcher = Pattern.compile("\\$\\{([^}]*)\\}").matcher(template);
        while (myMatcher.find()) {
            final String field = myMatcher.group(1);
            myMatcher.appendReplacement(sb, "");
            sb.append(processToken(field));
        }
        myMatcher.appendTail(sb);
        return sb.toString();
    }

    private String processToken(final String field) {
        if (this.tokens.containsKey(field)) {
            return this.tokens.get(field);
        } else {
            throw new RuntimeException("Unknown token: " + field);
        }
    }

    private String processJavascriptTemplate(final String template) throws ScriptException {
        final StringBuffer sb = new StringBuffer();
        final Matcher myMatcher = Pattern.compile("javascript\\{([^}]*)\\}").matcher(template);

        while (myMatcher.find()) {
            final String field = myMatcher.group(1);
            myMatcher.appendReplacement(sb, "");
            sb.append(processJavascript(field));
        }
        myMatcher.appendTail(sb);
        return sb.toString();
    }

    public String processJavascript(final String script) throws ScriptException {
        if (driver instanceof JavascriptExecutor) {
            return String.valueOf(((JavascriptExecutor) driver).executeScript("return " + script));
        } else {
            throw new UnsupportedOperationException("JavaScript not supported for driver");
        }
    }

    public void runCommand(String action, String target, String value) throws ScriptException, ReflectiveOperationException {

        target = processString(target);
        value = processString(value);

        final boolean wait;
        if (action.endsWith("AndWait")) {
            action = action.substring(0, action.length() - "AndWait".length());
            wait = true;
        } else {
            wait = false;
        }
        try {
            invokeAction(action, value, target, wait);

        } catch (java.lang.reflect.InvocationTargetException ex) {
            throw new RuntimeException(ex.getCause());
        }
    }

    public boolean isActionSupported(final String action) {
        final Object commandRunner = getInstanceForAction(action);
        return getMethodByAction(commandRunner, action) != null;
    }

    private void invokeAction(final String action, final String value, final String target, final boolean wait)
            throws ReflectiveOperationException {
        //Find method for action
        final Object commandRunner = getInstanceForAction(action);
        final Method method = getMethodByAction(commandRunner, action);
        if (method != null) {

            invokeMethodAction(commandRunner, method, target, value);

            if (wait) {
                WAIT_INSTANCE.waitForPageToLoad();
            }
        } else {
            throw new UnsupportedOperationException(String.format(UNSUPPORTED_ACTION, new Object[]{action}));
        }
    }

    private void invokeMethodAction(final Object instance, final Method method, final String target, final String value)
            throws ReflectiveOperationException {
        final int numberOfMethodArgurments = method.getParameterTypes().length;
        switch (numberOfMethodArgurments) {
            case 2:
                method.invoke(instance, target, value);
                break;
            case 1:
                method.invoke(instance, target);
                break;
            case 0:
                method.invoke(instance);
                break;
        }
    }

    private Method getMethodByAction(final Object object, final String name) {
        for (final Method method : object.getClass().getMethods()) {
            if (method.getName().equalsIgnoreCase(name)) {
                return method;
            }
        }
        return null;
    }

    private Object getInstanceForAction(final String name) {
        if (startsWithIgnoreCase(name, "Assert")) {
            return ASSERT_INSTANCE;
        } else if (startsWithIgnoreCase(name, "Wait")) {
            return WAIT_INSTANCE;
        } else {
            return COMMAND_INSTANCE;
        }
    }

    private String processString(String str) throws ScriptException {
        str = processTokens(str);
        if (str.contains("javascript")) {
            str = processJavascriptTemplate(str);
        }
        return str;
    }
}
